package com.stooscloud.core.redis;

import java.util.ArrayList;
import java.util.List;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.exceptions.JedisConnectionException;
@Slf4j
public class IdGenerator {

	/**
	 * JedisPool, luaSha
	 */
	List<Pair<JedisPool, String>> jedisPoolList;
	int retryTimes;
	int index = 0;
	private IdGenerator() {}
	private IdGenerator(List<Pair<JedisPool, String>> jedisPoolList,
			int retryTimes) {
		this.jedisPoolList = jedisPoolList;
		this.retryTimes = retryTimes;
	}

	static public IdGeneratorBuilder builder() {
		return new IdGeneratorBuilder();
	}
	static class IdGeneratorBuilder {
		List<Pair<JedisPool, String>> jedisPoolList = new ArrayList();
		int retryTimes = 5;
		public IdGeneratorBuilder addHost(String host, int port, int timeout, String password, String luaSha) {
			JedisPoolConfig config = new JedisPoolConfig();
			config.setMaxTotal(200);
			config.setMaxIdle(50);
			config.setMinIdle(8);//设置最小空闲数
			config.setMaxWaitMillis(10000);
			config.setTestOnBorrow(true);
			config.setTestOnReturn(true);
			//Idle时进行连接扫描
			config.setTestWhileIdle(true);
			//表示idle object evitor两次扫描之间要sleep的毫秒数
			config.setTimeBetweenEvictionRunsMillis(30000);
			//表示idle object evitor每次扫描的最多的对象数
			config.setNumTestsPerEvictionRun(10);
			//表示一个对象至少停留在idle状态的最短时间，然后才能被idle object evitor扫描并驱逐；这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义
			config.setMinEvictableIdleTimeMillis(60000);
			jedisPoolList.add(Pair.of(new JedisPool(config,host, port, timeout, password), luaSha));
			return this;
		}
		public IdGeneratorBuilder retryTimes(int retryTimes) {
			this.retryTimes = retryTimes;
			return this;
		}
		public IdGenerator build() {
			return new IdGenerator(jedisPoolList, retryTimes);
		}
	}
	public long next(String tab) {
		return next(tab, 0);
	}
	public long next(String tab, long shardId) {
		for (int i = 0; i < retryTimes; ++i) {
			Long id = innerNext(tab, shardId);
			if (id != null) {
				return id;
			}
		}
		throw new RuntimeException("Can not generate id!");
	}

	Long innerNext(String tab, long shardId) {
		index++;
		Pair<JedisPool, String> pair = jedisPoolList.get(index % jedisPoolList.size());
		JedisPool jedisPool = pair.getLeft();
		String luaSha = pair.getRight();
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			List<Long> result = (List<Long>) jedis.evalsha(luaSha, 2, tab, ""
					+ shardId);
			long id = buildId(result.get(0), result.get(1), result.get(2),
					result.get(3));
			return id;
		} catch (JedisConnectionException e) {
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			log.error("generate id error!", e);
		} finally {
			if (jedis != null) {
				jedisPool.returnResource(jedis);
			}
		}
		return null;
	}

	public static long buildId(long second, long microSecond, long shardId,
			long seq) {
		long miliSecond = (second * 1000 + microSecond / 1000);
		return (miliSecond << (12 + 10)) + (shardId << 10) + seq;
	}

	public static List<Long> parseId(long id) {
		long miliSecond = id >>> 22;
		long second = System.currentTimeMillis();
		long shardId = (id & (0xFFF << 10)) >> 10;
		long seq = id & 0x3FF;
		List<Long> re = new ArrayList<Long>(4);
		re.add(second);
		re.add(miliSecond);
		re.add(shardId);
		re.add(seq);
		return re;
	}
}
